{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 11.2 TensorBoard计算图可视化\n",
    "图11.1给出了一个TensorFlow计算图的可视化效果图。然而，从TensorBoard可视化结果中可以获取的信息远不止这些。本节将详细介绍如何更好地利TensorFlow计算图的可视化结果。\n",
    "\n",
    "### 11.2.1 命名空间与TensorBoard图上节点\n",
    "在11.1节给出的样例程序中只定义了一个有两个加数的加法操作，然而从图11.1中可以看到里面总共有4个节点。多出来的一个节点就是变量的初始化过程中系统生成的。更重要的是，这些节点的排列可能会比较乱，这导致主要的计算节点可能被埋没在大量信息量不大的节点中，使得可视化得到的效果图很难理解。**虽然图11.1中得到的可视化结果还是比较满意的，但是当神经网络模型的结构更加复杂、运算更多时，其所对应的TensorFlow计算图会比11.1节中简单的向量加法样例程序的计算图复杂很多，那么没有经过整理得到的可视化效果图可能就无法很好地帮助理解神经网络模型的结构了。**\n",
    "\n",
    "**为了更好地组织可视化效果图中的计算节点，TensorBoard支持通过TensorFlow命名空间来整理可视化效果图上的节点。在TensorBoard的默认视图中，TensorFlow计算图中同一个命名空间下的所有节点会被缩略成一个节点，只有顶层命名空间中的节点才会被显示在TensorBoard可视化效果图上。**在5.3节中已经介绍过变量的命名空间，以及如何通过`tf.variable_scope`函数管理变量的命名空间。除此之外，`tf.name_scope`函数也提供了命名空间管理的功能。这两个函数在大部分情况下是等价的，唯一的区别是在使用`tf.get_variable`函数时。以下代码简单地说明了这两个函数的区别:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "foo/bar:0\n",
      "bar/bar:0\n",
      "a/Variable:0\n",
      "b:0\n"
     ]
    }
   ],
   "source": [
    "import tensorflow as tf\n",
    "\n",
    "# 1. 不同的命名空间\n",
    "with tf.variable_scope(\"foo\"):\n",
    "    # 在命名空间foo下获取变量bar，于是得到的变量名为:foo/bar\n",
    "    a = tf.get_variable(\"bar\", [1])\n",
    "    print(a.name)                                   # 输出：foo/bar:0\n",
    "\n",
    "with tf.variable_scope(\"bar\"):\n",
    "    # 在命名空间bar下获取变量bar，于是得到的变量名为:bar/bar\n",
    "    # 此时变量bar/bar和变量foo/bar并不冲突，于是可以正常运行\n",
    "    b = tf.get_variable(\"bar\", [1])\n",
    "    print(b.name)                                  # 输出：bar/bar:0\n",
    "    \n",
    "# 2. tf.Variable和tf.get_variable的区别\n",
    "with tf.name_scope(\"a\"):\n",
    "    # 使用tf.Variable函数生成变量会受tf.name_scope影响\n",
    "    a = tf.Variable([1])\n",
    "    print(a.name)                                 # 输出：a/Variable:0\n",
    "    \n",
    "    # 使用tf.get_variable函数不受tf.name_scope影响\n",
    "    a = tf.get_variable(\"b\", [1])\n",
    "    print(a.name)                                 # 输出：b:0\n",
    "    \n",
    "# 由于tf.get_varibale不受name_scope影响，所以这里会报声明重复错误\n",
    "# with tf.name_scope(\"b\"):\n",
    "#     tf.get_variable(\"b\", [1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通过以下代码，可以改进11.1节中向量相加的样例代码，使得可视化得到的效果图更加清晰“"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "\n",
    "# 将输入定义放进各自的命名空间，从而使得TensorBoard可以根据\n",
    "# 命名空间来整理可视化效果图上的节点\n",
    "with tf.name_scope(\"input1\"):\n",
    "    input1 = tf.constant([1.0, 2.0, 3.0], name=\"input2\")\n",
    "with tf.name_scope(\"input2\"):\n",
    "    input2 = tf.Variable(tf.random_uniform([3]), name=\"input2\")\n",
    "output = tf.add_n([input1, input2], name=\"add\")\n",
    "\n",
    "writer = tf.summary.FileWriter(\"log/simple_example1.log\", tf.get_default_graph())\n",
    "writer.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "下图显示了改进后的可视化效果图，可以看出图11.1中用于初始化的节点已经被缩略起来了，这样TensorFLow程序中定义的加法运算被清晰地展示了出来。需要查看input2节点中具体包含了哪些运算时，可以将鼠标移动到input2节点，并点开右上角的加号。图11.4显示了展开input2节点之后的视图，可以看到数据初始化相关的操作被整理到了一起。\n",
    "<p align='center'>\n",
    "    <img src=images/图11.3.JPG>\n",
    "    <center>图11-3 改进后向量加法程序TensorFlow计算图可视化效果图</center>\n",
    "    <img src=images/图11.4.JPG>\n",
    "    <center>图11-4 展开input2节点的可视化效果图</center>\n",
    "</p>\n",
    "\n",
    "下面将给出一个样例程序来展示如何很好地可视化一个真实的神经网络结构图。本节将继续采用5.5节中给出的架构，以下代码给出了改造后的mnist_train.py程序:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:From <ipython-input-1-47a083ccadbb>:76: read_data_sets (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Please use alternatives such as official/mnist/dataset.py from tensorflow/models.\n",
      "WARNING:tensorflow:From d:\\python3\\tfgpu\\dl+\\lib\\site-packages\\tensorflow\\contrib\\learn\\python\\learn\\datasets\\mnist.py:260: maybe_download (from tensorflow.contrib.learn.python.learn.datasets.base) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Please write your own downloading logic.\n",
      "WARNING:tensorflow:From d:\\python3\\tfgpu\\dl+\\lib\\site-packages\\tensorflow\\contrib\\learn\\python\\learn\\datasets\\mnist.py:262: extract_images (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Please use tf.data to implement this functionality.\n",
      "Extracting ../../datasets/MNIST_data\\train-images-idx3-ubyte.gz\n",
      "WARNING:tensorflow:From d:\\python3\\tfgpu\\dl+\\lib\\site-packages\\tensorflow\\contrib\\learn\\python\\learn\\datasets\\mnist.py:267: extract_labels (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Please use tf.data to implement this functionality.\n",
      "Extracting ../../datasets/MNIST_data\\train-labels-idx1-ubyte.gz\n",
      "WARNING:tensorflow:From d:\\python3\\tfgpu\\dl+\\lib\\site-packages\\tensorflow\\contrib\\learn\\python\\learn\\datasets\\mnist.py:110: dense_to_one_hot (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Please use tf.one_hot on tensors.\n",
      "Extracting ../../datasets/MNIST_data\\t10k-images-idx3-ubyte.gz\n",
      "Extracting ../../datasets/MNIST_data\\t10k-labels-idx1-ubyte.gz\n",
      "WARNING:tensorflow:From d:\\python3\\tfgpu\\dl+\\lib\\site-packages\\tensorflow\\contrib\\learn\\python\\learn\\datasets\\mnist.py:290: DataSet.__init__ (from tensorflow.contrib.learn.python.learn.datasets.mnist) is deprecated and will be removed in a future version.\n",
      "Instructions for updating:\n",
      "Please use alternatives such as official/mnist/dataset.py from tensorflow/models.\n"
     ]
    }
   ],
   "source": [
    "import tensorflow as tf\n",
    "from tensorflow.examples.tutorials.mnist import input_data\n",
    "import mnist_inference\n",
    "\n",
    "# 1. 定义神经网络的参数\n",
    "BATCH_SIZE = 100\n",
    "LEARNING_RATE_BASE = 0.8\n",
    "LEARNING_RATE_DECAY = 0.99\n",
    "REGULARIZATION_RATE = 0.0001\n",
    "TRAINING_STEPS = 3000\n",
    "MOVING_AVERAGE_DECAY = 0.99\n",
    "\n",
    "\n",
    "# 2. 定义训练的过程并保存TensorBoard的log文件\n",
    "def train(mnist):\n",
    "    #  输入数据的命名空间。\n",
    "    with tf.name_scope('input'):\n",
    "        x = tf.placeholder(tf.float32, [None, mnist_inference.INPUT_NODE], name='x-input')\n",
    "        y_ = tf.placeholder(tf.float32, [None, mnist_inference.OUTPUT_NODE], name='y-input')\n",
    "    regularizer = tf.contrib.layers.l2_regularizer(REGULARIZATION_RATE)\n",
    "    y = mnist_inference.inference(x, regularizer)\n",
    "    global_step = tf.Variable(0, trainable=False)\n",
    "    \n",
    "    # 处理滑动平均的命名空间。\n",
    "    with tf.name_scope(\"moving_average\"):\n",
    "        variable_averages = tf.train.ExponentialMovingAverage(MOVING_AVERAGE_DECAY, global_step)\n",
    "        variables_averages_op = variable_averages.apply(tf.trainable_variables())\n",
    "   \n",
    "    # 计算损失函数的命名空间。\n",
    "    with tf.name_scope(\"loss_function\"):\n",
    "        cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y, labels=tf.argmax(y_, 1))\n",
    "        cross_entropy_mean = tf.reduce_mean(cross_entropy)\n",
    "        loss = cross_entropy_mean + tf.add_n(tf.get_collection('losses'))\n",
    "    \n",
    "    # 定义学习率、优化方法及每一轮执行训练的操作的命名空间。\n",
    "    with tf.name_scope(\"train_step\"):\n",
    "        learning_rate = tf.train.exponential_decay(\n",
    "            LEARNING_RATE_BASE,\n",
    "            global_step,\n",
    "            mnist.train.num_examples / BATCH_SIZE,\n",
    "            LEARNING_RATE_DECAY,\n",
    "            staircase=True)\n",
    "\n",
    "        train_step = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)\n",
    "\n",
    "        with tf.control_dependencies([train_step, variables_averages_op]):\n",
    "            train_op = tf.no_op(name='train')\n",
    "    \n",
    "    # 训练模型。\n",
    "    with tf.Session() as sess:\n",
    "        tf.global_variables_initializer().run()\n",
    "        for i in range(TRAINING_STEPS):\n",
    "            xs, ys = mnist.train.next_batch(BATCH_SIZE)\n",
    "            _, loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x: xs, y_: ys})\n",
    "\n",
    "            if i % 1000 == 0:\n",
    "                print(\"After %d training step(s), loss on training batch is %g.\" % (step, loss_value))\n",
    "    \n",
    "    # 将当前计算图中输出到TensorBoard日志文件\n",
    "    writer = tf.summary.FileWriter(\"log/mnist.log\", tf.get_default_graph())\n",
    "    writer.close()\n",
    "    \n",
    "    \n",
    "# 3. 主函数\n",
    "def main(argv=None): \n",
    "    mnist = input_data.read_data_sets(\"../../datasets/MNIST_data\", one_hot=True)\n",
    "    train(mnist)\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    tf.app.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*上面一个cell中程序在jupyter中会一直报错，参考[同样的问题](https://github.com/tensorflow/tensorflow/issues/9829)，选择使用.py来运行（文件见同目录mnist.py）。*\n",
    "\n",
    "相比5.5节中给出的mnist_train.py程序，以上程序最大的改变就是将完成类似功能的计算放到了由`tf.name_scope`函数生成的上下文管理器中。这样TensorBoard可以将这些节点有效地合并，从而突出神经网络的整体结构。因为在mnist_inference.py程序中已经使用了`tf.variable_scope`来管理变量的命名空间，所以这里不需要再做调整。下图展示了新的MNIST程序的TensorFlow计算图可视化得到的效果图（注意下面三幅图和书本中并不一致，因为书本这里采用的是下一节的代码）：\n",
    "<p align='center'>\n",
    "    <img src=images/图11.5.JPG>\n",
    "    <center>图11-5 改进后的MNIST样例程序TensorBoard计算图可视化效果图</center>\n",
    "</p>\n",
    "\n",
    "从图11.5中可以看到，**TensorBoard可视化效果图很好地展示了整个神经网络的结构**。在图11.5中:\n",
    "- input节点代表了训练神经网络需要的输入数据，这些输入数据会提供给神经网络的第一层layer1。然后神经网络第一层layer1的结果会被传到第二层layer2，进过layer2的计算得到前向传播的结果。\n",
    "- loss_function节点表示计算损失函数的过程，这既依赖于前向传播的结果来计算交叉熵（layer2到loss_function的边），又依赖于每一层中所定义的变量来计算L2正则化损失（layer1和layer2到loss_function的边）。loss_function的计算结果会提供给神经网络的优化过程，也就是图中train_step所代表的节点。\n",
    "\n",
    "在图11.5中可以发现节点之间由两种不同的边：\n",
    "1. **一种边是通过实线表示的，这种边刻画了数据传输，边上箭头方向表达了数据传输的方向**。比如layer1和layer2之间的边表示了layer1的输出将会作为layer2的输入。TensorBoard可视化效果图的边上还标注了张量的维度信息。比如节点input和layer1之间传输的张量的维度为？×784，这说明了训练时提供的batch大小不是固定的（也就是定义的时候是None），输入层节点的个数为784 。**当两个节点之间传输的张量多于1时，可视化效果图上将只显示张量的个数。效果图上边的粗细表示的是两个节点之间传输的标量维度的总大小，而不是传输的标量个数**。比如layer2和train_step之间虽然传输了6个张量，但其维度都比较小，所以这条边比layer1和moving_average之间的边（只传输了4个张量）还要细。当张量的维度无法确定时，TensorBoard会使用最细的边来表示，比如layer1与layer2之间的边。\n",
    "- **另外一种边是通过虚线表示的，表达了计算之间的依赖关系，**比如在程序中通过`tf.control_dependencies`函数指定了更新参数滑动平均值的操作和通过反向传播更新变量的操作需要同时进行，于是moving_average与train_step之间存在一条虚边。\n",
    "\n",
    "**除了手动的通过TensorFlow中的命名空间来调整TensorBoard的可视化效果图，TensorBoard也会智能地调整可视化效果图上的节点。**TensorFlow中部分计算节点会有比较多的依赖关系，如果全部画在一张图上会便可视化得到的效果图非常拥挤。于是TensorBoard将TensorFlow计算图分成了主图（Main Graph）和辅助图（Auxiliary nodes)，如上图所示。\n",
    "<p align='center'>\n",
    "    <img src=images/图11.7a.JPG>\n",
    "    <center>(a) 手工将TensorFlow计算图可视化效果图中节点移出主图</center>\n",
    "    <img src=images/图11.7b.JPG>\n",
    "    <center>图11-7(b) 手工将TensorFlow计算图可视化效果图中节点加入主图</center>\n",
    "</p>\n",
    "\n",
    "**除了自动的方式，TensorBoard也支持手工的方式来调整可视化结果。**如上图(a)所示，右键单击可视化效果图上的节点会弹出一个选项，这个选项可以将节点加入主图或者从主图中删除。左键选择一个节点并点击信息框下部的选项也可以完成类似的功能。上图(b)展示了将train_step节点从主图中移出后的效果。注意TensorBoard不会保存用户对计算图可视化结果的手工修改，页面刷新之后计算图可视化结果又会回到最初的样子。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 11.2.2 节点信息\n",
    "**除了展示TensorFlow计算图的结构，TensorBoard还可以展示TensorFlow计算图上每个节点的基本信息以及运行时消耗的时间和空间。**本节将进一步讲解如何通过TensorBoard展现TensorFlow计算图节点上的这些信息。TensorFlow计算节点的运行时间都是非常有用的信息，它可以帮助更加有针对性地优化TensorFlow程序，使得整个程序的运行速度更快。使用TensorBoard可以非常直观地展现所有TensorFlow计算节点在某一次运行时所消耗的时间和内存。将以下代码加入11.2.1节中修改后的mnist_train.py神经网络训练部分，就可以将不同迭代轮数的每个TensorFlow计算节点的运行时间和消耗的内存写入TensorBoard的日志文件中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "with tf.Session() as sess:\n",
    "    tf.global_variables_initializer().run()\n",
    "    \n",
    "    for i in range(TRAINING_STEPS):\n",
    "        xs, ys = mnist.train.next_batch(BATCH_SIZE)\n",
    "        if i % 1000 == 0:\n",
    "            # 配置运行时需要记录的信息\n",
    "            run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)\n",
    "            # 运行时记录运行信息的proto\n",
    "            run_metadata = tf.RunMetadata()\n",
    "            # 将配置信息和记录运行的proto传入运行的过程，从而记录运行时每一个节点的时间、内存信息\n",
    "            _, loss_value, step = sess.run([train_op, loss, global_step],\n",
    "                                            feed_dict={x: xs, y_:ys},\n",
    "                                            options=run_options,\n",
    "                                            run_metadata=run_metadata)\n",
    "            # 将节点在运行时的信息写入日志文件\n",
    "            train_writer.add_run_metadata(run_metadata, 'step%03d'%i)\n",
    "            print(\"After %d training step(s), loss on training batch is %g\" % (step, loss_value))\n",
    "    else:\n",
    "        _, loss_value, step = sess.run([train_op, loss, global_step], feed_dict={x: xs, y_:ys})      \n",
    "'''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "再次运行文件（见同目录mnist_info.py），并使用这个程序输出的日志启动TensorBoard，这样就可以可视化每个TensorFlow计算节点在某一次运行时所消耗的时间和空间。如图11.8(a)所示，点击页面左侧的Session runs选项，这时就会出现一个下拉单，在这个下拉单中会出现所有通过`train_writer.add_run_metadata`函数记录的运行数据。如图11.8(b)所示，选择一次运行后，TensorBoard左侧的Color栏中Compute time和Memory这两个选项将可以被选择。\n",
    "<p align='center'>\n",
    "    <img src=images/图11.8a.JPG>\n",
    "    <center>图11.8(a) 选择运行记录的页面</center>\n",
    "    <img src=images/图11.8b.JPG>\n",
    "    <center>图11.8(b) 选择完运行记录后Color多出来的选项</center>\n",
    "</p>\n",
    "\n",
    "**在Color栏中选择Compute time可以看到在这次运行中每个TensorFlow计算节点的运行时间。类似的，选择Memory可以看到这次运行中每个TensorFlow计算节点所消耗的内存。**图11.9展示了在第9000轮迭代时，不同TensorFlow计算节点时间消耗的可视化效果图。图中颜色越深的节点表示时间消耗越大。从图11.9中可以看出，代表训练神经网络的train_step节点消耗的时间是最多的。通过对每一个计算节点消耗时间的可视化，可以很容易地找到TensorFlow计算图上的性能瓶颈，这大大方便了算法优化的工作。在性能调优时，一般会选择迭代轮数较大时的数据作为不同计算节点时间／空间消耗的标准， 因为这样可以减少TensorFlow初始化对性能的影响。\n",
    "<p align='center'>\n",
    "    <img src=images/图11.9.JPG>\n",
    "    <center>图11.9 第9000轮迭代时不同TensorFlow计算节点时间消耗的可视化效果图</center>\n",
    "</p>\n",
    "\n",
    "在TensorBoard界面左侧的Color栏中，除了Compute time和Memory，还有Structure和Device两个选项(XLA还处于试验阶段，这里不做详细介绍。由于TPU只在谷歌内部使用，所以这里也不做介绍)。\n",
    "- **Structure**。前面图中展示的可视化效果图都是使用默认的Structure选项。在这个视图中，灰色的节点表示没有其他节点和它拥有相同结构。如果有两个节点的结构相同，那么它们会被涂上相同的颜色；\n",
    "- **Device**。这个选项可以根据TensorFlow计算节点运行的机器给可视化效果图上的节点染色。在使用GPU时，可以通过这种方式直观地看到哪些计算节点被放到了GPU上。具体如何使用GPU将在第12章介绍。\n",
    "\n",
    "**信息卡片：当点击TensorBoard可视化效果图中的节点时，界面的右上角会弹出一个信息卡片显示这个节点的基本信息。**\n",
    "\n",
    "- **当点击节点为一个命名空间时**，TensorBoard展示的信息卡片有这个命名空间内所有计算节点的输入、输出以及依赖关系。虽然属性（attributes)也会展示在卡片中，但是在代表命名空间的属性下不会有任何内容。当Session runs选择了某一次运行时，节点的信息卡片上也会出现这个节点运行时所消耗的时间和内存等信息。如图11.11所示:\n",
    "<p align='center'>\n",
    "    <img src=images/图11.11.JPG>\n",
    "</p>\n",
    "\n",
    "- **当点击节点对应一个TensorFlow计算节点时**，TensorBoard也会展示类似的信息。图11.12展示了一个TensorFlow计算节点所对应的信息卡片。在TensorBoard页面中，空心的小椭圆对应了TensorFlow计算图上的一个计算节点，而一个矩形对应了计算图上的一个命名空间。**TensorFlow计算节点所对应的信息卡片中的内容和命名空间信息卡片相似，只是TensorBoard可以将TensorFlow计算节点的属性也展示出来。**例如在图11.12中，属性栏下显示了选中的计算节点是在什么设备上运行的，以及运行这个计算时的两个参数。\n",
    "<p align='center'>\n",
    "    <img src=images/图11.12.JPG>\n",
    "</p>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
